<?php

require_once 'Ec.class.php';
require_once 'Stream.class.php';


function BEGIN($group = NULL) {
	static $value = NULL;
	if (NULL !== $group) {
		//echo "*** BEGIN: $group\n";
		$value = $group;
	}
	return $value;
}


class Lex {
	var $nxt = array();
	var $initial = 1;
	var $last_state = 1;
	var $accept = array();
	var $groups = array('INITIAL' => 1);
	
	function compile($file, $outfile = 'scanner.inc') {
		$this->load($file);
		$this->gen2($outfile);
	}
	
	
	function gen2($file = 'scanner.inc') {
		$f = fopen($file, 'w');
		fprintf($f, "<?php\n");
		fprintf($f, "require_once 'Lex.class.php';\n");
		fprintf($f, "\$lex = new Lex;\n");
		fprintf($f, "\$lex->last_state = {$this->last_state};\n");

		$s = serialize($this->accept);
		$s = addcslashes($s, "\0..\37\"\$\\");
		fprintf($f, "\$lex->accept = unserialize(\"%s\");\n", $s);
		
		$s = serialize($this->nxt);
		$s = addcslashes($s, "\0..\37\"\$\\");
		fprintf($f, "\$lex->nxt = unserialize(\"%s\");\n", $s);
		fclose($f);
	}


	function gen3($file = 'scanner.inc') {
		$f = fopen($file, 'w');
		fprintf($f, "<?php\n");
		fprintf($f, "require_once 'Lex.class.php';\n");
		fprintf($f, "\$lex = new Lex;\n");
		fprintf($f, "\$lex->last_state = {$this->last_state};\n");
		fprintf($f, "\$lex->nxt = json_decode('");
		$s = json2_encode($this->nxt);
		$s = addcslashes($s, "'");
		fprintf($f, $s);
		fprintf($f, "');\n");
		fclose($f);
	}
	
	
	
	function load($file) {
		$f = fopen($file, 'r');
		//$cnt = 0;
		while (FALSE !== ($line = fgets($f))) {
			//$line = trim($line);
			//if (++$cnt > 100) break;
			
			if (substr($line, 0, 2) == '//')
				continue;
			
			if (!preg_match("/^(<([a-zA-Z_][a-zA-Z_0-9]*)>)?((\"(\\\\.|\\\\|[^\r\n\"\\\\]+)+\")|((\\\\.|\\\\|[^ \\\\{\t\r\n]+)+))[ \t]*{?(.*)}?/", $line, $match))
				continue;
				
			//var_dump($match);
			
			$group = $match[2] ? $match[2] : 'INITIAL';
			
			if ($match[4])
				$json = $match[4];
			else if ($match[6]) {
				$json = $match[6];
				$json = preg_replace("/([^\\\\])\"/", '\1\"', $json);
				$json = '"' . $json . '"';
			}
			
			$code = $match[8];
			$code = preg_replace("/\b(BEGIN\s*\()([a-zA-Z0-9_]+)(\))/", '\1"\2"\3', $code);
			//echo "json: [$json]\n\n";
			
			$tok = json_decode($json);
			
			//echo "line: [$line]\n\n";
			//echo "tok: [$tok]\n\n";
			
			//echo "code: [$code]\n\n";
			
			
			if (FALSE === $tok) {
				echo "FATAL: tok is FALSE! json: $json";
				die;
			}			
			//printf("%05d  %s\n", strlen($tok), $tok);
			
			$this->add_rule($group, $tok, $code);
		}
		fclose($f);
	}
	
	function gen($file = 'scanner.inc') {
		$f = fopen($file, 'w');
		fprintf($f, "<?php\n");
		fprintf($f, "require_once 'Lex.class.php';\n");
		fprintf($f, "\$lex = new Lex;\n");
		fprintf($f, "\$lex->last_state = {$this->last_state};\n\n");

		fprintf($f, "\$lex->groups = array(\n");
		foreach ($this->groups as $k => $v) {
			fprintf($f, "  '%s' => %d,\n", $k, $v);
		}
		fprintf($f, ");\n\n");

		
		fprintf($f, "\n");
		fprintf($f, "\$lex->accept = array_flip(array(%s));\n\n", join(', ', array_keys($this->accept)));
		
		
		fprintf($f, "\$lex->nxt = array(\n");
		
		for ($i = 0; $i < $this->last_state; ++$i) {
			$v =& $this->nxt[$i];
			$row = array();
			if ($v)
				foreach ($v as $c => $n) {
					$c = addslashes($c);
					$row[] = "\"$c\" => $n";
				}
			$row = "$i => array(" . join(', ', $row) . "), ";
			fprintf($f, "  %s\n", $row);		
		}
				
		fprintf($f, ");\n");

		fprintf($f, "\n");
		fprintf($f, "function __lex_accept_callback(\$tok, \$yytext, \$left) {\n");
		fprintf($f, "  switch (\$tok) {\n");
		foreach ($this->accept as $k => $v) {
			if (!$v) continue;
			fprintf($f, "    case %d: $v; break;\n", $k);
		}
		fprintf($f, "  }\n");
		fprintf($f, "};\n\n");
		
		fclose($f);
	}
	
	
	function run($stream) {
		//$state = isset($this->state) ? $this->state : $this->initial;
		BEGIN('INITIAL');
	
		$lex_accept_callback = function_exists('__lex_accept_callback');
	
		$left = '';
	
		while (TRUE) {			
			//$state = $this->initial;
			$group = BEGIN();
			$state = $this->groups[$group];
		
			$char = $stream->getch();

			if ($stream->eof())
				break;

			//echo "*** state $state: left: [$left]; next char: $char\n";
		
			$yytext = $char;
			$last_accept_state = NULL;
			$last_accept_position = NULL;


			while ($s = $this->nxt[$state][$char]) {
				
				$state = $s;
				
				if (isset($this->accept[$state])) {
					$last_accept_position = $stream->pos;
					$last_accept_state = $state;
					$last_accept_yytext = $yytext;
					//$last_accept_left = $left;
					//echo "    acceptable: [$yytext]\n";
				}
				$char = $stream->getch();

				//$left .= $char;
				$yytext .= $char;
				//echo "    $yytext.\n";	
			}
			
			
			//$left .= $yytext;
		
			if (!$last_accept_state) {
				//echo "STOP as position {$stream->pos}\n";
				//break;
				$left .= $char;
				
				continue;
			}
		
			//if ($last_accept_state != $state) {
				$state = $last_accept_state;
				$yytext = $last_accept_yytext;
				if (NULL !== $last_accept_position)	
					$stream->pos = $last_accept_position;
				//$left = $last_accept_left;
			//}

			//BEGIN('INITIAL');
			
			if (!$left && $_left)
				$left = $_left;

			if ($lex_accept_callback)
				__lex_accept_callback($state, $yytext, $left);
			else		
				echo "MATCH: [{$yytext}]\n";
				
			if ($left)
				$_left = $left;
			$left = '';
			
			$this->state = $state;
			$this->yytext = $yytext;
			
		}
	}

	
	function add_rule($group, $str, $code = NULL) {
		$stream = new Stream($str);
		$out = array();
		//$state = $this->initial;
		
		if (isset($this->groups[$group]))
			$state = $this->groups[$group];
		else {
			$state = ++$this->last_state;
			$this->groups[$group] = $state;
		}

		while (FALSE !== ($char = $stream->getch())) {
			$s = $this->nxt[$state][$char];
			if (!$s) break;
			$state = $s;
		}

		do {
			$s = ++$this->last_state;
			$this->nxt[$s] = array();
			$this->nxt[$state][$char] = $s;
			$state = $s;
		} while (FALSE !== ($char = $stream->getch()));

		$this->accept[$state] = $code;
		//$this->accept_code[$state] = $code;
	}
	
	
	function dump() {
		echo "nxt: \n";
		for ($i = $this->initial, $max = $this->last_state; $i <= $max; ++$i) {
			$row = array();
			if (isset($this->accept[$i]))
				$row[] = 'ACCEPT: ' . $this->accept[$i];
			else {
				if ($this->nxt[$i])
					foreach ($this->nxt[$i] as $k => $v) {
						$k = addcslashes($k, "\0..\37");
						$row[] = "$k => $v";
					}
			}
			printf("%3d: %s\n", $i, join(', ', $row));
		}
	}
	

}

